/*********************************************************************************
Organization 					: 				Georgia Institute of Technology
  
Institute 						:				Cognitive Computing Group(CCL)
 
Authors							: 				Santi Onta��n
												Kane Bonnette
 												Sooraj Bhat
 												
Class							:				Unit 
 
Function						: 				Class to hold the unit information
****************************************************************************/
package base;

import java.io.Serializable;

import org.jdom.Element;

import util.XMLWriter;



/**
 * Reimplementation of Unit.cpp in Java
 * Note that it also handles buildings
 * Santi Edits:
 * 	- disentangled this class from the proxybot so that it can be reused for other purposes
 */


public class WargusUnit implements Serializable {

	/**
	 * The globally unique, never repeated ID of this unit - 
	 * even after this unit dies, this number will not be reused!
	 */
	int m_unitID;

	/**
	 * Until other wise noted, I'm using the following values
	 * for unit types. Since Orcs/Humans have the same units, the value
	 * is the same for the equivalent unit.
	 *	-1, -1, unused
	 *	0, 1, unit-footman unit-grunt
	 *	2, 3, unit-peasant unit-peon
	 *	4, 5, unit-ballista unit-catapult
	 *	6, 7, unit-knight unit-ogre
	 *	8, 9, unit-archer unit-axethrower
	 *	10, 11, unit-mage unit-death-knight
	 *  12, 13, unit-paladin, unit-ogre-mage
	 *	14, 15, unit-dwarves unit-goblin-sappers
	 *	26, 27, unit-human-oil-tanker unit-orc-oil-tanker
	 *	28, 29, unit-human-transport unit-orc-transport
	 *	30, 31, unit-elven-destroyer unit-troll-destroyer
	 *	32, 33, unit-battleship unit-ogre-juggernaught
	 *	38, 39, unit-gnomish-submarine unit-giant-turtle
	 *	40, 41, unit-gnomish-flying-machine unit-goblin-zeppelin
	 *	42, 43, unit-gryphon-rider unit-dragon
	 *  45,     eye of kilrogg
	 *  54,     skeleton
	 *  57,     critter
	 *	58, 59, unit-farm unit-pig-farm
	 *	60, 61, unit-human-barracks unit-orc-barracks
	 *	62, 63, unit-church unit-altar-of-storms
	 *	64, 65, unit-human-watch-tower unit-orc-watch-tower
	 *	66, 67, unit-stables unit-ogre-mound
	 *	68, 69, unit-gnomish-inventor unit-goblin-alchemist
	 *	70, 71, unit-gryphon-aviary unit-dragon-roost
	 *	72, 73, unit-human-shipyard unit-orc-shipyard
	 *	74, 75, unit-town-hall unit-great-hall
	 *	76, 77, unit-elven-lumber-mill unit-troll-lumber-mill
	 *	78, 79, unit-human-foundry unit-orc-foundry
	 *	80, 81, unit-mage-tower unit-temple-of-the-damned
	 *	82, 83, unit-human-blacksmith unit-orc-blacksmith
	 *	84, 85, unit-human-refinery unit-orc-refinery
	 *	88, 89, unit-keep unit-stronghold
	 *	90, 91, unit-castle unit-fortress
	 *  96, 97, unit-human-guard-tower
	 *  98, 99, unit-human-cannon-tower
	 *  92, 93	unit-goldmine, unit-oil-patch
	 *	103,104 unit-human-wall unit-orc-wall
	 */
	UnitType m_type;
	int m_lifetime = 0;
	int m_x,m_y;
	int m_hitPoints; 			// The current hit points of this unit
	int m_magicPoints; 			// The current magic points of this unit
	int m_resourcesCarrying; 	// how much gold/wood/oil the unit is carrying
	int m_kills; 				// How many kills this unit has
	int m_lastAttacked;			// Last cycle that the unit was attacked
	int m_playerID; 			//what player the unit belongs to.
	
	double m_heading;			//0-2*pi radians 0 E, 90 N, W 180, S 270
	boolean m_moving;          // true/false if moving is moving
	boolean m_randomly; 
	int m_moving_count;
	
	String m_status;
	
	static boolean on_Farm = false;
	static boolean on_Barrack = false;
	

	/**
	 * 0 is the currently performing action
	 * 1 and 2 are the status arguments that relate to each status flag - some
	 * statuses use both flags, others, just one - check GetUnitStatus() 
	 * in stratagus/src/socket/socket.c
	 */
	int[] m_statusFlags = {-1, -1, -1}; // Status flag for this unit
	
	public WargusUnit(int inc_unitID, int inc_type, int inc_hitPoints, int mp, int inc_locX,
			int inc_locY, int inc_rAmount, int inc_kills, int lastAttacked,
			int[] stat, int player) {
		m_unitID = inc_unitID;
		m_type = UnitType.getUnitTypeEnumFromNum(inc_type);
		m_x = inc_locX;
		m_y = inc_locY;
		m_hitPoints = inc_hitPoints;
		m_magicPoints = mp;
		m_resourcesCarrying = inc_rAmount;
		m_kills = inc_kills;
		m_lastAttacked = lastAttacked;
		m_statusFlags = stat;
		m_playerID = player;
				
	}
	
	public WargusUnit(int unitid,int player,String type,int x,int y,int currenthitpoints, int currentmagicpoints)
	{
		this(unitid, WargusStateImporter.unitTypeToInteger(type), currenthitpoints, currentmagicpoints, x, y, 0, 0, 0, new int[3], player);
	}
	
	public WargusUnit(int unitid,int player,String type,int x,int y,int currenthitpoints, int currentmagicpoints, String status)
	{
		this(unitid, WargusStateImporter.unitTypeToInteger(type), currenthitpoints, currentmagicpoints, x, y, 0, 0, 0, new int[3], player);
		
		int[] statusInt = new int[3];
		m_statusFlags[0] = WargusStateImporter.statusToInteger(status);
		m_statusFlags[1] = 0;
		m_statusFlags[2] = 0;
	}	

	public WargusUnit(WargusUnit u)
	{
		m_unitID = u.m_unitID;
		m_type = u.m_type;
		m_x = u.m_x;
		m_y = u.m_y;
		m_hitPoints = u.m_hitPoints;
		m_magicPoints = u.m_magicPoints;
		m_resourcesCarrying = u.m_resourcesCarrying;
		m_kills = u.m_kills;
		m_statusFlags[0] = u.m_statusFlags[0];
		m_statusFlags[1] = u.m_statusFlags[1];
		m_statusFlags[2] = u.m_statusFlags[2];
		m_playerID = u.m_playerID;	
	}	

	/**
	 * finds the cycles left until the unit is destroyed,
	 * or how long the unit has been alive.
	 * eye of kilrogg is limited to 765 cycles
	 * skeletons are limited to 25,500 cycles
	 * corpses are limited to 1,000 cycles
	 * everything else just returns time since it finished training
	 * @return positive if cycles alive, negative if counting down to doom.
	 */
	public int getLifetime() {
		switch (m_type) {
		case CORPSE: //corpses
			return -(1000 - m_lifetime);
		case EYE_OF_KILROGG: //eye of kilrogg
			return -(765 - m_lifetime);
		case SKELETON: //skeleton
			return -(255000 - m_lifetime);
		default:
			return m_lifetime;
		}
	}

	/**
	 * current hit points of the unit
	 * @return hitpoints
	 */
	public int getHitPoints() {
		return m_hitPoints;
	}

	/**
	 * maximum HP of given unit. if -1 is returned, unit type
	 * is unknown. Should only be daemons and the like;
	 * all units the play can use are handled. 
	 * @return max HP
	 */
	public int getMaxHP() {
		return m_type.getMaxHP();
	}

	/**
	 * returns current number of magic points
	 * @return current MP
	 */
	public int getMagicPoints() {
		return m_magicPoints;
	}

	/**
	 * returns maximum MP based on class
	 * only affects mage, death-knight, paladin & ogre-magi
	 * @return max MP
	 */
	public int getMaxMP() {
		return m_type.getMaxMP();
	}

	/**
	 * if the unit has no hit points, it must be dead
	 * @return true if dead, false if alive
	 */
	public boolean IsDead() {
		if (m_hitPoints < 1) {
			return true;
		}
		return false;
	}

	/**
	 * factors in class and upgrades to find max possible damage
	 * @return maximum amount of damage
	 */
	public int getMaxDamage(boolean[] advances) {
		return m_type.getMaxDamage(advances);
	}


	/**
	 * factors in class and upgrades to find min possible damage
	 * @return minimum amount of damage
	 */
	public int getMinDamage(boolean[] advances) {
		return m_type.getMinDamage(advances);
	}

	/**
	 * factors in class and upgrades to find range of unit's attack
	 * 0 if the unit has no attack
	 * @return range of unit's attack
	 */
	public int getRange(boolean[] advances) {
		return m_type.getRange(advances);
	}
	
	/**
	 * base speed of unit. does not handle hasting
	 * 0 if the unit cannot move
	 * @return speed of unit
	 */
	public int getSpeed() {
		return m_type.getSpeed();
	}

	/**
	 * factors in class and upgrades to find unit's defense
	 * 0 if the unit has no defense
	 * @return unit's defense
	 */
	public int getArmor(boolean[] advances) {
		return m_type.getArmor(advances);
	}
	
	
	public float distance(WargusUnit u,float wt,float wp,float wpos,float whp,float ws) {
		float dt = 1;
		float dpos = 1;
		float dhp = 1;
		float dstatus = 1;
		float dplayer = 1;
		
		if (m_type == u.m_type) dt=0;
		
		{
			float d = (float)Math.sqrt((m_x-u.m_x)*(m_x-u.m_x) + (m_y-u.m_y)*(m_y-u.m_y));
			dpos = Math.min(d/10,1.0f);
		}
		
		{
			float tmp1,tmp2;
			float pu1 = 1;
			float pu2 = 1;
			tmp1 = m_type.getMaxHP();//WargusStateImporter.unitMaxHitPoints(m_type);
			tmp2 = u.m_type.getMaxHP();//WargusStateImporter.unitMaxHitPoints(u.m_type);
			if (tmp1>0) {
				pu1 = m_hitPoints / m_type.getMaxHP();//WargusStateImporter.unitMaxHitPoints(m_type);
			} else {
			}
			if (tmp2>0) {
				pu2 = u.m_hitPoints / u.m_type.getMaxHP();//WargusStateImporter.unitMaxHitPoints(u.m_type);
			} else {
			}
			if (tmp1>0 && tmp2>0) dhp = Math.abs(pu1-pu2);
		}
		
		if (m_statusFlags[0] == u.m_statusFlags[0]) dstatus = 0; 
		
		if( m_playerID == u.m_playerID) dplayer = 0;
		
//		System.out.println("DD: " + dt + " " + dplayer + " " + dpos + " " + dhp + " " + dstatus);
		return wt*dt + wp*dplayer+ wpos*dpos + whp*dhp + ws*dstatus;
	}	
	


	/**
	 * returns all pertinent info about a Unit
	 * @ return the unit info
	 */
	public String toString(boolean[] advances) {
		String result = "Unit - ";
		result += "id: " + m_unitID;
		result += " type: " + m_type;
		result += " location: (" + m_x + " " + m_y + ")";
		result += " HP: " + getHitPoints() + "/" + getMaxHP();
		result += " MP: " + getMagicPoints() + "/" + getMaxMP();
		result += " Damage: " + getMinDamage(advances) + "/" + getMaxDamage(advances);
		result += " Range: " + getRange(advances);
		result += " Armor: " + getArmor(advances);
		result += " CurrentAction: " + m_statusFlags[0];
		return result;
	}
	
	public String toBriefString() {
		return "ID: " + m_unitID + "[" + m_playerID + "] type: " + WargusStateImporter.unitTypeToString(m_type.getUnitTypeNum()) + " (" + m_x + "," + m_y + ")";
	}

	public void writeXML(XMLWriter w) {
		// Dead bodies or destroyed places:
		if (m_type.getUnitTypeNum() >= 105 && m_type.getUnitTypeNum() <= 109) return;
		
		w.tag("unit id=\"" + m_unitID + "\"");
		{
			w.tag("type", WargusStateImporter.unitTypeToString(m_type.getUnitTypeNum()));
			
			w.tag("player", m_playerID);
			w.tag("x", m_x);
			w.tag("y", m_y);
			w.tag("current-hit-points", getHitPoints());
			w.tag("current-magic-points", getMagicPoints());
			
			w.tag("status");			
			{
				w.tag("action", WargusStateImporter.statusToString(m_statusFlags[0]));
				w.tag("arg1", m_statusFlags[1]);
				w.tag("arg2", m_statusFlags[2]);
			}
			w.tag("/status");
			
			w.tag("last-attacked", m_lastAttacked);
			w.tag("resources", m_resourcesCarrying);
			w.tag("kills", m_kills);
		}
		w.tag("/unit");
	}
	
	// special writer to handle null units
	public static void writeXML(XMLWriter w, WargusUnit u) {
		if (u == null) {
			w.tag("unit", "");
		} else {
			u.writeXML(w);
		}
	}
	
	public static WargusUnit loadFromXML(Element e) {
		assert e.getName().equals("unit") : 
			"WargusUnit.loadFromXML: Invalid XML Element " + e.getName();
		if (e.getChildren().size() == 0) return null; // handles null units
		
		int unitID = Integer.parseInt(e.getAttributeValue("id"));
		int type = WargusStateImporter.unitTypeToInteger(e.getChildText("type"));
		int playerID = Integer.parseInt(e.getChildText("player"));
		int x = Integer.parseInt(e.getChildText("x"));
		int y = Integer.parseInt(e.getChildText("y"));
		
		int hp = Integer.parseInt(e.getChildText("current-hit-points"));
		int mp = Integer.parseInt(e.getChildText("current-magic-points"));
		
		Element statusElement = e.getChild("status");
		int action = WargusStateImporter.statusToInteger(statusElement.getChildText("action"));
		int arg1 = Integer.parseInt(statusElement.getChildText("arg1"));
		int arg2 = Integer.parseInt(statusElement.getChildText("arg2"));
				
		int lastAttacked = Integer.parseInt(e.getChildText("last-attacked"));
		int resourcesCarrying = Integer.parseInt(e.getChildText("resources"));
		int kills = Integer.parseInt(e.getChildText("kills"));
		
		return new WargusUnit(unitID, type, hp, mp, x, y, resourcesCarrying, 
				kills, lastAttacked, new int[]{action, arg1, arg2}, playerID);
	}
	
	public boolean inRangeOf(WargusUnit u) {
		double d = Math.sqrt((m_x-u.m_x)*(m_x-u.m_x) + (m_y-u.m_y)*(m_y-u.m_y)) - 0.5; // substracting 0.5 to correct the rounding that stratagus does
		
		//System.out.println("Is " + m_unitID + " in range of " + u.m_unitID + "? range = " + u.getRange(null) + " distance = " + d);
		if (d<u.getRange(null)) return true;
		return false;
	}
	
	public boolean inRangeOf(WargusUnit u,boolean []advances) {
		double d = Math.sqrt((m_x-u.m_x)*(m_x-u.m_x) + (m_y-u.m_y)*(m_y-u.m_y)) - 0.5; // substracting 0.5 to correct the rounding that stratagus does
		
		System.out.println("Is " + m_unitID + " in range of " + u.m_unitID + "? range = " + u.getRange(advances) + " distance = " + d);
		if (d<u.getRange(null)) return true;
		return false;
	}

	/**
	 * just a setter
	 * @param hitPoints
	 */
	public void setHitPoints(int hitPoints) {
		m_hitPoints = hitPoints;
	}

	/**
	 * just a getter
	 * @return value
	 */
	public long getKills() {
		return m_kills;
	}

	/**
	 * just a setter
	 * @param kills
	 */
	public void setKills(int kills) {
		m_kills = kills;
	}

	/**
	 * just a getter
	 * @return value
	 */
	public int getLocX() {
		return m_x;
	}

	/**
	 * just a setter
	 * @param locX
	 */
	public void setLocX(int locX) {
		m_x = locX;
	}

	/**
	 * just a getter
	 * @return value
	 */
	public int getLocY() {
		return m_y;
	}

	/**
	 * just a setter
	 * @param locY
	 */
	public void setLocY(int locY) {
		m_y = locY;
	}

	/**
	 * just a setter
	 * @param magicPoints
	 */
	public void setMagicPoints(int magicPoints) {
		m_magicPoints = magicPoints;
	}

	/**
	 * just a getter
	 * @return value
	 */
	public int getRAmount() {
		return m_resourcesCarrying;
	}

	/**
	 * just a setter
	 * @param amount
	 */
	public void setRAmount(int amount) {
		m_resourcesCarrying = amount;
	}

	/**
	 * just a getter
	 * @return value
	 */
	public int[] getStatus() {
		return m_statusFlags;
	}

	/**
	 * just a setter
	 * @param status
	 */
	public void setStatus(int[] status) {
		m_statusFlags = status;
	}

	/**
	 * just a getter
	 * @return value
	 */
	public int getType() {
		return m_type.getUnitTypeNum();
	}

	public UnitType getTypeEnum() {
		return m_type;
	}
	
	/**
	 * just a setter
	 * @param type
	 */
	public void setType(int type) {
		m_type = UnitType.getUnitTypeEnumFromNum(type);
	}
	
	public void setType(UnitType type) {
		m_type = type;
	}

	/**
	 * just a getter
	 * @return value
	 */
	public int getUnitID() {
		return m_unitID;
	}

	/**
	 * just a setter
	 * @param unitID
	 */
	public void setUnitID(int unitID) {
		m_unitID = unitID;
	}

	/**
	 * just a setter
	 * @param lifetime
	 */
	public void setLifetime(int lifetime) {
		m_lifetime = lifetime;
	}

	public int getPlayerID() {
		return m_playerID;
	}

	public void setPlayerID(int playerID) {
		m_playerID = playerID;
	}
	
	public int getLastAttacked() {
		return m_lastAttacked;
	}

	public void setHeading(double new_heading) {
		m_heading = new_heading;
		
	}
	
	public double getHeading() {
		return m_heading;
	}
	
	public void setMoving(boolean is_moving) {
		m_moving = is_moving;
	}
	
	public boolean getMoving() {
		return m_moving;
	}
	
	public void setRandom(boolean is_random) {
		m_randomly = is_random;
	}
	
	public boolean getRandom() {
		return m_randomly;
	}
	
	public void setMovingCount(int moving_count) {
		m_moving_count = moving_count;
	}
	
	public int getMovingCount() {
		return m_moving_count;
	}
	
	public void setUnitStatus(String new_status) {
		if (this.m_type == UnitType.HUMAN_PEASANT) {
			//System.out.println("WokerUnit(" + this.m_unitID + "): from " + m_status + " -> " + new_status);
		}
		m_status = new_status;
	}
	
	public void setUnitStatusInitial(String new_status) {
		if (this.m_type == UnitType.HUMAN_PEASANT) {
			//System.out.println("Intial: WokerUnit(" + this.m_unitID + "): from " + m_status + " -> " + new_status);
		}
		m_status = new_status;
	}
	
	public String getUnitStatus() {
		return m_status;
	}
	
	public static int UnitWidth( int unitType ) {
		return UnitType.getUnitTypeEnumFromNum(unitType).getUnitWidth();
	}
	
	public static int UnitHeight( int unitType ) {
		return UnitType.getUnitTypeEnumFromNum(unitType).getUnitHeight();
	}
	
	public static void setOnFarm(boolean m_bool) {
		on_Farm = m_bool;
	}
	
	public static void setOnBarrack(boolean m_bool) {
		on_Barrack = m_bool;
	}
	
	public static boolean getOnFarm() {
		return on_Farm;
	}
	
	public static boolean getOnBarrack() {
		return on_Barrack;
	}
}
